package com.itheima.nio.tomcat.domain;

import org.apache.commons.io.IOUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.List;

/**
 * 这个类的作用：封装我们响应数据，给客户端进行数据响应
 */
public class HttpResponse {

    private String version  ;       // Http响应协议的版本
    private String status ;         // Http响应的状态码
    private String des;             // 状态码的说明信息

    private List<Header> headers = new ArrayList<Header>() ;            // 封装响应头数据
    private String contentType;     // contentType属性，告知服务端给客户端响应数据的MIME类型

    // 作用：接收SelectionKey，后期我们需要从SelectionKey中获取通道信息然后给出响应
    private SelectionKey selectionKey ;
    public HttpResponse(SelectionKey selectionKey) {
        this.selectionKey = selectionKey ;
    }

    // 定义该属性的作用，从HttpRequest对象中获取客户端请求服务器资源对应的资源路径，然后根据指定的路劲给出客户端响应的响应
    private HttpRequest httpRequest ;

    // 指定服务器资源所对应的路径
    public static final String WEB_APP_PATH = "itheima-nio-tomcat\\webapp" ;

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getDes() {
        return des;
    }

    public void setDes(String des) {
        this.des = des;
    }

    public List<Header> getHeaders() {
        return headers;
    }

    public void setHeaders(List<Header> headers) {
        this.headers = headers;
    }

    public String getContentType() {
        return contentType;
    }

    public void setContentType(String contentType) {

        // 1. 创建一个Header对象，然后将这个Header对象添加到headers集合中
        Header header = new Header();
        header.setName("Content-Type");
        header.setValue(contentType);
        headers.add(header) ;

        this.contentType = contentType;
    }

    public SelectionKey getSelectionKey() {
        return selectionKey;
    }

    public void setSelectionKey(SelectionKey selectionKey) {
        this.selectionKey = selectionKey;
    }

    public HttpRequest getHttpRequest() {
        return httpRequest;
    }

    public void setHttpRequest(HttpRequest httpRequest) {
        this.httpRequest = httpRequest;
    }

    // 给客户端进行响应，根据业务逻辑处理的结果，响应数据
    public void write(String content){

        // 按照我们的Http响应协议的格式给客户端传输数据
        // 构建响应行
        this.version = "HTTP/1.1" ;
        this.status = "200" ;
        this.des = "OK" ;
        String responseLine = this.version + " " + this.status + " " + this.des + "\r\n" ;

        // 构建响应头
        StringBuilder sb = new StringBuilder() ;
        for(Header header : headers) {
            sb.append(header.getName()).append(": ").append(header.getValue()).append("\r\n") ;
        }
        String responseHeader = sb.toString();

        // 响应空行
        String emptyLine = "\r\n" ;

        // 把响应行， 响应头，响应空行拼接成一个字符串
        String result = responseLine + responseHeader + emptyLine ;

        try {

            // 写数据
            SocketChannel socketChannel = (SocketChannel)selectionKey.channel() ;
            ByteBuffer byteBuffer = ByteBuffer.wrap(result.getBytes("UTF-8"));
            socketChannel.write(byteBuffer) ;

            // 写响应体数据
            ByteBuffer body = ByteBuffer.wrap(content.getBytes("UTF-8"));
            socketChannel.write(body) ;
            socketChannel.close();

        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    // 作用：向通道写数据，给出客户端响应
    public void sendStaticResource(){

        /**
         * 给出客户端响应的实现思路：
         * 1. 构建响应行字符串(给HttpResponse的相关属性设置指定的值)，在做字符串的拼接
         * 2. 构建响应头，设置ContentType响应头（setContentType方法重构，当调用这个方法的时候，我们去创建一个Header对象，将对象添加到headers集合中）
         * 3. 准备响应空行数据
         * 4. 将响应行、响应头。响应空行数据写会给用户浏览器
         * 5. 在将响应体数据写会给用户浏览器
         * 6. 释放资源
         */
        // 构建响应行字符串(给HttpResponse的相关属性设置指定的值)，在做字符串的拼接
        this.version = "HTTP/1.1";

        // 我们只需要在服务端去判断当前用户所访问的资源在服务器端是否存在，如果不存在将这个状态码设置为404 , 并且指定一个状态码的描述信息
        // 如何判断这个资源是否存在？把用户所要访问的资源通过File对象进行表示，然后调用File对象的方法去判断这个资源是否存在
        String requestURI = httpRequest.getRequestURI();

        // 提高服务器的健壮性
        if("".equals(requestURI) || requestURI == null) {
            return  ;
        }

        File file = new File(WEB_APP_PATH + requestURI) ;
        if(file.exists()) {
            this.status = "200" ;
            this.des = "OK" ;
        }else {
            this.status = "404" ;
            this.des = "NOT FOUND" ;
        }

        String responseLine = this.version + " " + this.status + " " + this.des + "\r\n";

        // 构建响应头,我们只需要去指定一个响应头：Content-Type ， 这个响应头用来指定我们服务器端给客户端响应的数据的MIME类型
        /**
         * 现在我们可能只设置这一个响应头，后期可能会存在很多个响应头数据，而多个响应头数据是存储在headers，而我们需要去构建响应头字符串，那么我们就需要遍历这个
         * 集合。获取每一个响应头数据做字符串的拼接
         * 我们这个Content-Type响应头信息如何进行设置呢？
         *
         * 思路：
         * 1. 直接创建一个Header对象，把这个Header对象直接添加到headers中
         * 2. 对我们的setContentType这个方法进行重构
         *
         * 我们本次使用第二种，因为一旦我们对这个setContentType这个方法进行了重构，那么后期在其他类中我们也可以调用这个方法，把头信息存储到headers中
         */
        // this.setContentType("text/html;charset=UTF-8");
        if(!this.status.equals("404")) {
            if(requestURI.endsWith(".txt")) {
                this.setContentType("text/html;charset=UTF-8");
            }else if(requestURI.endsWith(".jpg")) {
                this.setContentType("image/jpeg");
            }
        }else {
            this.setContentType("text/html;charset=UTF-8");
        }

        StringBuilder sb = new StringBuilder() ;
        for(Header header : headers) {
            sb.append(header.getName()).append(": ").append(header.getValue()).append("\r\n") ;
        }
        String responseHeader = sb.toString() ;

        // 准备响应空行数据
        String emptyLine = "\r\n" ;

        // 响应行，响应头，响应空行的字符串
        String responseLineAndHeaderAndEmptyLine = responseLine + responseHeader + emptyLine ;

        try {

            // 把responseLineAndHeaderAndEmptyLine响应给客户端
            SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
            ByteBuffer byteBuffer = ByteBuffer.wrap(responseLineAndHeaderAndEmptyLine.getBytes("UTF-8"));
            socketChannel.write(byteBuffer) ;

            // 写响应体数据；这个响应体数据就是我们服务器向客户端响应的真实数据
            // String content = "你好NIO Http服务器...." ;
            // ByteBuffer buffer = ByteBuffer.wrap(content.getBytes("UTF-8"));
            ByteBuffer buffer = ByteBuffer.wrap(getContent()) ;
            socketChannel.write(buffer) ;

            // 释放资源
            socketChannel.close();

        }catch ( Exception e) {
            e.printStackTrace();
        }

    }

    // 就是指定服务端给客户端所响应的数据对应的字节数组
    public byte[] getContent() {

        try{

            if(this.status.equals("200")) {

                // 获取客户端需要访问服务端时所对应的资源路径
                String requestURI = httpRequest.getRequestURI();
                if(requestURI.endsWith("favicon.ico")) {
                    return "无次图标文件...".getBytes("UTF-8") ;
                }else {
                    // 读取我们服务端的文本数据中的内容，将其转换成字节数组。 怎么实现呢？我们可以去使用commons-io
                    FileInputStream fileInputStream = new FileInputStream(WEB_APP_PATH + requestURI) ;
                    byte[] bytes = IOUtils.toByteArray(fileInputStream);
                    return bytes ;
                }

            }else {
                return  "对不起，不存在你要访问的服务器资源".getBytes("UTF-8") ;
            }

        }catch ( Exception e) {
            e.printStackTrace();
        }

        return null ;

    }

}
